﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Data;
using System.Data.SqlClient;
using System.Reflection;
using System.ComponentModel;
using System.Xml.Serialization;
using System.IO;
using System.Diagnostics;

namespace WyUnity.Utility.ExtentionMethods
{

	/// <summary>
	/// 类型转换，格式化，空值检查 等等。
	/// </summary>
	public static class TypeConversionExtensions
	{
		#region Functions

		#region FormatToString

		/// <summary>
		/// Calls the object's ToString function passing in the formatting
		/// </summary>
		public static string FormatToString(this object input, string format)
		{
			if (input == null)
				return "";
			return !string.IsNullOrEmpty(format) ? (string)CallMethod("ToString", input, format) : input.ToString();
		}

		#endregion

		#region IsNotDefault

		/// <summary>
		/// Determines if the object is not null
		/// </summary>
		public static bool IsNotDefault<T>(this T obj, IEqualityComparer<T> equalityComparer = null)
		{
			return !equalityComparer.NullCheck(new GenericEqualityComparer<T>()).Equals(obj, default(T));
		}

		#endregion

		#region IsDefault

		/// <summary>
		/// Determines if the object is null
		/// </summary>
		public static bool IsDefault<T>(this T obj, IEqualityComparer<T> equalityComparer = null)
		{
			return equalityComparer.NullCheck(new GenericEqualityComparer<T>()).Equals(obj, default(T));
		}

		#endregion

		#region IsNotNullOrDBNull

		public static bool IsNotNull(this object obj)
		{
			return obj != null;
		}

		/// <summary>
		/// 确定对象是否为Null或者DBNull
		/// </summary>
		public static bool IsNotNullOrDBNull(this object obj)
		{
			return obj != null && !Convert.IsDBNull(obj);
		}

		#endregion

		#region IsNullOrDBNull

		public static bool IsNull(this object obj)
		{
			return obj == null;
		}

		/// <summary>
		/// 确定对象是否为Null或者DBNull
		/// </summary>
		public static bool IsNullOrDBNull(this object obj)
		{
			return obj == null || Convert.IsDBNull(obj);
		}

		#endregion

		#region NullCheck

		/// <summary>
		/// 当对象为空时，使用传入的方法进行构建
		/// </summary>
		public static T NullCheck<T>(this T obj, Func<T> constructor)
		{
			return obj == null ? constructor() : obj;
		}
		/// <summary>
		/// 当对象为空时，使用默认值
		/// </summary>
		public static T NullCheck<T>(this T obj, T defaultValue = default(T))
		{
			return obj == null ? defaultValue : obj;
		}
		/// <summary>
		/// 检查对象是否为空，当为空时使用new创建
		/// </summary>
		public static T NullCheck<T>(this T obj) where T : new()
		{
			return obj == null ? new T() : obj;
		}
		#endregion

		#region ThrowIfDefault

		/// <summary>
		/// 当对象的值为默认值时，抛出ArgumentNullException。
		/// </summary>
		public static void ThrowIfDefault<T>(this T item, string name, IEqualityComparer<T> equalityComparer = null)
		{
			if (item.IsDefault(equalityComparer))
				throw new ArgumentNullException(name);
		}

		#endregion

		#region ThrowIfNull

		/// <summary>
		/// 当对象的值为空时，抛出ArgumentNullException
		/// </summary>
		[DebuggerHidden]
		public static void ThrowIfNull(this object item, string name)
		{
			if (item == null)
				throw new ArgumentNullException(name);
		}

		/// <summary>
		/// 当对象的值为空时，抛出PreConditionException
		/// </summary>
		[DebuggerHidden]
		public static void ThrowPreConditionExceptionIfNull(this object item, string message, params object[] args)
		{
			if (item == null)
				throw new PreConditionException(message, args);
		}
		/// <summary>
		/// 当对象的值为空时，抛出PreConditionException
		/// </summary>
		[DebuggerHidden]
		public static void ThrowPreConditionExceptionIfNull(this object item, string name)
		{
			if (item == null)
				throw new PreConditionException("前置条件验证失败：" + name + "不能为空");
		}
		#endregion

		#region ThrowIfNullOrEmpty

		/// <summary>
		/// 当字符串的值为空或长度为0时，抛出ArgumentNullException
		/// </summary>
		[DebuggerHidden]
		public static void ThrowIfNullOrEmpty<T>(this IEnumerable<T> item, string name)
		{
			if (item.IsNullOrEmpty())
				throw new ArgumentNullException(name);
		}

		#endregion

		#region ThrowIfNullOrDBNull

		/// <summary>
		/// 当对象的值为空或dbnull时，抛出ArgumentNullException
		/// </summary>
		public static void ThrowIfNullOrDBNull(this object item, string name)
		{
			if (item.IsNullOrDBNull())
				throw new ArgumentNullException(name);
		}

		#endregion

		#region ToSQLDbType

		/// <summary>
		/// Converts a .Net type to SQLDbType value
		/// </summary>
		public static SqlDbType ToSQLDbType(this Type type)
		{
			return type.ToDbType().ToSqlDbType();
		}

		/// <summary>
		/// Converts a DbType to a SqlDbType
		/// </summary>
		public static SqlDbType ToSqlDbType(this DbType type)
		{
			SqlParameter Parameter = new SqlParameter();
			Parameter.DbType = type;
			return Parameter.SqlDbType;
		}

		#endregion

		#region ToDbType

		/// <summary>
		/// Converts a .Net type to DbType value
		/// </summary>
		/// <param name="type">.Net Type</param>
		/// <returns>The corresponding DbType</returns>
		public static DbType ToDbType(this Type type)
		{
			if (type == typeof(byte)) return DbType.Byte;
			else if (type == typeof(sbyte)) return DbType.SByte;
			else if (type == typeof(short)) return DbType.Int16;
			else if (type == typeof(ushort)) return DbType.UInt16;
			else if (type == typeof(int)) return DbType.Int32;
			else if (type == typeof(uint)) return DbType.UInt32;
			else if (type == typeof(long)) return DbType.Int64;
			else if (type == typeof(ulong)) return DbType.UInt64;
			else if (type == typeof(float)) return DbType.Single;
			else if (type == typeof(double)) return DbType.Double;
			else if (type == typeof(decimal)) return DbType.Decimal;
			else if (type == typeof(bool)) return DbType.Boolean;
			else if (type == typeof(string)) return DbType.String;
			else if (type == typeof(char)) return DbType.StringFixedLength;
			else if (type == typeof(Guid)) return DbType.Guid;
			else if (type == typeof(DateTime)) return DbType.DateTime;
			else if (type == typeof(DateTimeOffset)) return DbType.DateTimeOffset;
			else if (type == typeof(byte[])) return DbType.Binary;
			else if (type == typeof(byte?)) return DbType.Byte;
			else if (type == typeof(sbyte?)) return DbType.SByte;
			else if (type == typeof(short?)) return DbType.Int16;
			else if (type == typeof(ushort?)) return DbType.UInt16;
			else if (type == typeof(int?)) return DbType.Int32;
			else if (type == typeof(uint?)) return DbType.UInt32;
			else if (type == typeof(long?)) return DbType.Int64;
			else if (type == typeof(ulong?)) return DbType.UInt64;
			else if (type == typeof(float?)) return DbType.Single;
			else if (type == typeof(double?)) return DbType.Double;
			else if (type == typeof(decimal?)) return DbType.Decimal;
			else if (type == typeof(bool?)) return DbType.Boolean;
			else if (type == typeof(char?)) return DbType.StringFixedLength;
			else if (type == typeof(Guid?)) return DbType.Guid;
			else if (type == typeof(DateTime?)) return DbType.DateTime;
			else if (type == typeof(DateTimeOffset?)) return DbType.DateTimeOffset;
			return DbType.Int32;
		}

		/// <summary>
		/// Converts SqlDbType to DbType
		/// </summary>
		/// <param name="type">Type to convert</param>
		/// <returns>The corresponding DbType (if it exists)</returns>
		public static DbType ToDbType(this SqlDbType type)
		{
			SqlParameter parameter = new SqlParameter();
			parameter.SqlDbType = type;
			return parameter.DbType;
		}

		#endregion

		#region ToType

		/// <summary>
		/// Converts a SQLDbType value to .Net type
		/// </summary>
		/// <param name="type">SqlDbType Type</param>
		/// <returns>The corresponding .Net type</returns>
		public static Type ToType(this SqlDbType type)
		{
			return type.ToDbType().ToType();
		}

		/// <summary>
		/// Converts a DbType value to .Net type
		/// </summary>
		/// <param name="type">DbType</param>
		/// <returns>The corresponding .Net type</returns>
		public static Type ToType(this DbType type)
		{
			if (type == DbType.Byte) return typeof(byte);
			else if (type == DbType.SByte) return typeof(sbyte);
			else if (type == DbType.Int16) return typeof(short);
			else if (type == DbType.UInt16) return typeof(ushort);
			else if (type == DbType.Int32) return typeof(int);
			else if (type == DbType.UInt32) return typeof(uint);
			else if (type == DbType.Int64) return typeof(long);
			else if (type == DbType.UInt64) return typeof(ulong);
			else if (type == DbType.Single) return typeof(float);
			else if (type == DbType.Double) return typeof(double);
			else if (type == DbType.Decimal) return typeof(decimal);
			else if (type == DbType.Boolean) return typeof(bool);
			else if (type == DbType.String) return typeof(string);
			else if (type == DbType.StringFixedLength) return typeof(char);
			else if (type == DbType.Guid) return typeof(Guid);
			else if (type == DbType.DateTime2) return typeof(DateTime);
			else if (type == DbType.DateTime) return typeof(DateTime);
			else if (type == DbType.DateTimeOffset) return typeof(DateTimeOffset);
			else if (type == DbType.Binary) return typeof(byte[]);
			return typeof(int);
		}

		#endregion

		#region TryTo

		/// <summary>
		/// Attempts to convert the object to another type and returns the value
		/// </summary>
		/// <typeparam name="T">Type to convert from</typeparam>
		/// <typeparam name="R">Return type</typeparam>
		/// <param name="obj">Object to convert</param>
		/// <param name="defaultValue">Default value to return if there is an issue or it can't be converted</param>
		/// <returns>The object converted to the other type or the default value if there is an error or can't be converted</returns>
		public static R TryTo<R>(this object obj, R defaultValue = default(R))
		{
			try
			{
				if (obj.IsNullOrDBNull())
					return defaultValue;
				if ((obj as string) != null)
				{
					string objectValue = obj as string;
					if (typeof(R).IsEnum)
						return (R)System.Enum.Parse(typeof(R), objectValue, true);
					if (objectValue.IsNullOrEmpty())
						return defaultValue;
				}
				if ((obj as IConvertible) != null)
					return (R)Convert.ChangeType(obj, typeof(R));
				if (typeof(R).IsAssignableFrom(obj.GetType()))
					return (R)(object)obj;
				TypeConverter Converter = TypeDescriptor.GetConverter(obj.GetType());
				if (Converter.CanConvertTo(typeof(R)))
					return (R)Converter.ConvertTo(obj, typeof(R));
				if ((obj as string) != null)
					return obj.ToString().TryTo<R>(defaultValue);
			}
			catch { }
			return defaultValue;
		}
		public static object TryTo(this object obj, Type distinctType, object defaultValue)
		{
			try
			{
				if (obj.IsNullOrDBNull())
					return defaultValue;
				if ((obj as string) != null)
				{
					string objectValue = obj as string;
					if (distinctType.IsEnum)
						return System.Enum.Parse(distinctType, objectValue, true);
					if (objectValue.IsNullOrEmpty())
						return defaultValue;
				}
				if ((obj as IConvertible) != null)
					return Convert.ChangeType(obj, distinctType);
				if (distinctType.IsAssignableFrom(obj.GetType()))
					return obj;
				TypeConverter Converter = TypeDescriptor.GetConverter(obj.GetType());
				if (Converter.CanConvertTo(distinctType))
					return Converter.ConvertTo(obj, distinctType);
				if ((obj as string) != null)
					return obj.ToString().TryTo(distinctType, defaultValue);
			}
			catch { }
			return defaultValue;
		}
		public static object TryTo(this object obj, Type distinctType)
		{
			try
			{
				if (obj.IsNullOrDBNull())
					throw new Exception("类型转换失败:源类型不能为null");
				if ((obj as string) != null)
				{
					string objectValue = obj as string;
					if (distinctType.IsEnum)
						return System.Enum.Parse(distinctType, objectValue, true);
					if (objectValue.IsNullOrEmpty())
					{
						if (distinctType == typeof(string))
							return string.Empty;
						else
							throw new Exception("类型转换失败:字符串为空");
					}
				}
				if ((obj as IConvertible) != null)
					return Convert.ChangeType(obj, distinctType);
				if (distinctType.IsAssignableFrom(obj.GetType()))
					return obj;
				TypeConverter Converter = TypeDescriptor.GetConverter(obj.GetType());
				if (Converter.CanConvertTo(distinctType))
					return Converter.ConvertTo(obj, distinctType);
				if ((obj as string) != null)
					return obj.ToString().TryTo(distinctType);
			}
			catch (Exception)
			{
				throw;
			}
			throw new Exception("类型转换失败");

		}
		#endregion

		#region 序列化
		/// <summary>
		/// 将对象进行XML序列化，默认使用UTF8编码
		/// </summary>
		public static string SerializeToXml(this object obj, Encoding encode = null)
		{
			encode = encode.NullCheck(Encoding.UTF8);

			var s = new XmlSerializer(obj.GetType());

			using (var ms = new MemoryStream())
			{
				s.Serialize(ms, obj);
				ms.Position = 0;
				var b = new byte[ms.Length];
				ms.Read(b, 0, b.Length);
				return encode.GetString(b);
			}
		}

		/// <summary>
		/// 将XML反序列化为对象，默认使用UTF8编码
		/// </summary>
		public static T DeserializeFromXml<T>(this string xmlString, Encoding encode = null) where T : class
		{
			var s = new XmlSerializer(typeof(T));
			using (var ms = new MemoryStream(encode.NullCheck(Encoding.UTF8).GetBytes(xmlString)))
			{
				return s.Deserialize(ms) as T;
			}
		}
		#endregion

		#region 散列函数
		/// <summary>
		/// 计算字符串的MD5值，可用于密码的存储
		/// </summary>
		public static string ToHashString(this string sourceString)
		{
			var md5 = new System.Security.Cryptography.MD5CryptoServiceProvider();

			//将源字符串加上复杂的字符串之后再计算散列，增加安全性
			var hashed = md5.ComputeHash(Encoding.UTF8.GetBytes(sourceString + "*&uGm0p@d~Y"));

			return BitConverter.ToString(hashed, 4, 8).Replace("-", "");
		}
		#endregion

		#endregion
		#region Private Static Functions

		/// <summary>
		/// Calls a method on an object
		/// </summary>
		private static object CallMethod(string methodName, object obj, params object[] inputVariables)
		{
			if (string.IsNullOrEmpty(methodName) || obj == null)
				return null;
			Type objectType = obj.GetType();
			MethodInfo method = null;
			if (inputVariables != null)
			{
				Type[] methodInputTypes = new Type[inputVariables.Length];
				for (int x = 0; x < inputVariables.Length; ++x)
					methodInputTypes[x] = inputVariables[x].GetType();
				method = objectType.GetMethod(methodName, methodInputTypes);
				if (method != null)
					return method.Invoke(obj, inputVariables);
			}
			method = objectType.GetMethod(methodName);
			return method == null ? null : method.Invoke(obj, null);
		}

		#endregion
	}
}
